/*
 * Copyright Iain Sproat, 2008
 * 
 *
 */
using System;
using System.Text;
using System.IO;
using System.Collections.Generic;

using Newtonsoft.Json;
using FreeBase.Fluent;

namespace FreeBase.Fluent.MQLConverter
{
	/// <summary>
	/// Description of JsonMQLConverter.
	/// </summary>
	public class FluentMQLConverter : IMQLConverter
	{
		public string FluentToMQL( FBQuery query )
		{
			StringBuilder sb = new StringBuilder();
			StringWriter sw = new StringWriter( sb );
			
			JsonWriter jsonWriter = new JsonTextWriter( sw );
			jsonWriter.WriteStartObject();
			jsonWriter.WritePropertyName( "query" );
			if( query.IsArray )
			{
				jsonWriter.WriteStartArray();
				SerializeQuery( jsonWriter, query, false );
				jsonWriter.WriteEndArray();
			}
			else
				SerializeQuery( jsonWriter, query, false );
			jsonWriter.WriteEndObject();
			
			return sb.ToString();
		}
		
		#region Helper Methods for FluentToMQL
		private void SerializeQuery( JsonWriter jsonWriter, FBQuery query, bool IsANestedQuery )
		{
			if( jsonWriter == null )
				throw new ArgumentNullException( "jsonWriter" );
			
			jsonWriter.WriteStartObject();
			
			//placeholders
			foreach(FBPlaceholder place in query.Placeholders )
			{
				SerializePlaceholder( jsonWriter, place );
			}
			
			//criteria
			if( query.Criteria.Count > 0 )
			{
				IDictionary<string, IList<FBCriteria>> seperatedCriteria
					= Helper.SeperateCriteriaByProperty( query.Criteria );
				foreach(KeyValuePair<string, IList<FBCriteria>> entry in seperatedCriteria)
				{
					SerializeCriterium( jsonWriter, entry.Value, IsANestedQuery );
				}
			}
			
			//subqueries
			foreach( FBInnerQuery innerQuery in query.InnerQueries )
			{
				SerializeInnerQuery( jsonWriter, innerQuery );
			}
			
			//directives
			foreach(FBDirective dir in query.Directives)
			{
				SerializeDirective( jsonWriter, dir );
			}
			
			//end query
			jsonWriter.WriteEndObject();
		}
		
		
		private void SerializePlaceholder( JsonWriter jsonWriter, FBPlaceholder place )
		{
			
			switch( place.ShowManyRecords )
			{
				case NumberOfRecords.Many:
					if( place.IsReciprocal )
						jsonWriter.WritePropertyName( "!" + place.PropertyName );
					jsonWriter.WritePropertyName( place.PropertyName );
					jsonWriter.WriteStartArray();
					if( place.ShowAsObject )
					{
						jsonWriter.WriteStartObject();
						jsonWriter.WriteEndObject();
					}
					jsonWriter.WriteEndArray();
					break;
				case NumberOfRecords.One:
					jsonWriter.WritePropertyName( place.PropertyName );
					if( place.ShowAsObject )
					{
						jsonWriter.WriteStartObject();
						jsonWriter.WriteEndObject();
					}
					else
						jsonWriter.WriteNull();
					break;
				case NumberOfRecords.None:
					break; //do nothing
				default:
					goto case NumberOfRecords.One;
			}
		}
		
		
		private void SerializeCriterium( JsonWriter jsonWriter, IList<FBCriteria> crits, bool IsInNestedQuery )
		{
			if( jsonWriter == null )
				throw new ArgumentNullException( "jsonWriter" );
			if( crits == null )
				throw new ArgumentNullException( "crits" );
			
			for(int i = 0; i < crits.Count; i++)
			{
				FBCriteria currentCrit = crits[i];
				
				string prefix = string.Empty;
				if( crits.Count > 1 )
					prefix = "criteria" + i.ToString(); //Helper.GetCharacter( i );
				
				SerializeCriteria( jsonWriter, prefix, currentCrit );
			}
			
		}
		
		
		private void SerializeCriteria( JsonWriter jsonWriter, string prefix, FBCriteria crit )
		{
			if( jsonWriter == null )
				throw new ArgumentNullException( "jsonWriter" );
			if( string.IsNullOrEmpty( prefix ) )
				prefix = string.Empty; //prefix can be empty but not null
			if( crit == null )
				throw new ArgumentNullException( "crit" );
			if( string.IsNullOrEmpty( crit.PropertyName ) )
				throw new ArgumentException( "The criteria needs to be applied to a specific property on a freebase topic." +
				                            " Please assign a property to the criteria.");
			
			
			string propertyName = string.Empty;
			if( !string.IsNullOrEmpty( prefix ) )
				propertyName +=  prefix + ":";
			propertyName += crit.PropertyName;
			
			propertyName += Helper.SerializeComparisonOperator( crit );
			propertyName = propertyName.ToLower();
			jsonWriter.WritePropertyName( propertyName );
			
			if( crit.JoinType == JoinType.OR )
			{
				jsonWriter.WriteStartArray();
				for(int i = 0; i < crit.GetValues.Length; i++ )
				{
					string val = crit.GetValues[i];
					if( crit.IsNumericComparison )
					{
						//HACK there is a bug in the JsonWriter which omits the comma after the first value
						//and adds an extra comma to the last value
						if( i == 0 )
						{
							jsonWriter.WriteRawValue( val + "," );
						}
						else if( i == crit.GetValues.Length - 1 )
						{
							jsonWriter.WriteRaw( val );
							
						}
						else
						{
							jsonWriter.WriteRawValue( val );
						}
					}
					else
						jsonWriter.WriteValue( val );
				}
				jsonWriter.WriteEndArray();
			}
			else
			{
				if( crit.IsNumericComparison )
					jsonWriter.WriteRawValue( crit.GetValue );
				else
					jsonWriter.WriteValue( crit.GetValue );
			}
		}
		
		
		private void SerializeDirective( JsonWriter jsonWriter, FBDirective dir )
		{
			if( dir == null )
				throw new ArgumentNullException( "dir" );
			
			switch( dir.GetDirectiveType )
			{
				case DirectiveType.Limit:
					jsonWriter.WritePropertyName( "limit" );
					jsonWriter.WriteRawValue( dir.GetDirectiveValue[0].ToString() );
					break;
				case DirectiveType.Count:
					jsonWriter.WritePropertyName("count");
					jsonWriter.WriteNull();
					break;
				case DirectiveType.Return:
					jsonWriter.WritePropertyName("return");
					jsonWriter.WriteValue( dir.GetDirectiveValue[0].ToString() );
					break;
				case DirectiveType.Sort:
					jsonWriter.WritePropertyName("sort");
					WriteSortValues( jsonWriter, dir.GetDirectiveValue );
					break;
				case DirectiveType.Optional:
					jsonWriter.WritePropertyName("optional");
					jsonWriter.WriteValue( dir.GetDirectiveValue[0].ToString() );
					break;
				case DirectiveType.Create:
					jsonWriter.WritePropertyName("create");
					jsonWriter.WriteValue( dir.GetDirectiveValue[0].ToString() );
					break;
				default:
					throw new NotImplementedException( "This type of directive is not yet implemented in the FluentToMQLConverter" );
			}
		}
		
		private void WriteSortValues( JsonWriter jsonWriter, string[] sortValues )
		{
			if( jsonWriter == null )
				throw new ArgumentNullException( "jsonWriter" );
			if( sortValues == null )
				throw new ArgumentNullException( "sortValues" );
			if( sortValues.Length < 1 )
				throw new ArgumentNullException( "sortValues" );
			
			if( sortValues.Length > 1 )
				jsonWriter.WriteStartArray();
			foreach( string s in sortValues )
				jsonWriter.WriteValue( s );
			if( sortValues.Length > 1 )
				jsonWriter.WriteEndArray();
		}
		
		private void SerializeInnerQuery( JsonWriter jsonWriter, FBInnerQuery innerQuery )
		{
			jsonWriter.WritePropertyName( innerQuery.PropertyName.ToLower() );
			if( innerQuery.NumberOfExpectedRecordsFromInnerQuery == NumberOfRecords.Many )
				jsonWriter.WriteStartArray();
			SerializeQuery( jsonWriter, innerQuery.InnerQuery, true );
			if( innerQuery.NumberOfExpectedRecordsFromInnerQuery == NumberOfRecords.Many )
				jsonWriter.WriteEndArray();
		}
		
		#endregion
		
	}
}
